﻿// -------------------- iSharePoint -------------------- //
// 
//  Company:	IT Complex, LLC
//  Project:	SmartLib
//  File name:	SPPrincipalExts.cs
//  Developer:	Igor Solomatov
//  Created:	05.03.2013
// 
// -------------------- iSharePoint -------------------- //

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Microsoft.SharePoint;

namespace Smart.Common
{
	[Localizable(false)]
	public static class SPPrincipalExts
	{
		#region Groups

		/// <summary>
		/// Returns group from <paramref name="groups"/> collection by <paramref name="groupId"/>
		/// </summary>
		public static SPGroup SafeGetGroup(this SPGroupCollection groups, int groupId)
		{
			try
			{
				return groups.GetByID(groupId);
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.SafeGetGroupById", x);
				return null;
			}
		}

		/// <summary>
		/// Returns group from <paramref name="groups"/> collection by <paramref name="name"/>
		/// </summary>
		public static SPGroup SafeGetGroup(this SPGroupCollection groups, string name)
		{
			try
			{
				return groups[name];
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.SafeGetGroupById", x);
				return null;
			}
		}

		/// <summary>
		/// Checks whether <paramref name="groups"/> collection contains group by <paramref name="groupId"/>
		/// </summary>
		public static bool ContainsGroup(this SPGroupCollection groups, int groupId)
		{
			try
			{
				return groups.GetByID(groupId) != null;
			}
			catch (Exception x)
			{
				H.LT("SPPrincipalExts.ContainsGroup.{0}: {1}, {2}", x.GetType().Name, groupId, x.Message);
				return false;
			}
		}

		/// <summary>
		/// Checks whether <paramref name="groups"/> collection contains group by <paramref name="name"/>
		/// </summary>
		public static bool ContainsGroup(this SPGroupCollection groups, string name)
		{
			try
			{
				return groups[name] != null;
			}
			catch (Exception x)
			{
				H.LT("SPPrincipalExts.ContainsGroup.{0}: {1}, {2}", x.GetType().Name, name, x.Message);
				return false;
			}
		}

		/// <summary>
		/// Returns group cache
		/// </summary>
		private static CacheEntry<Cortege<HashSet<int>, HashSet<string>>> GetGroupsCacheEntry(string rootWebUrl)
		{
			return new CacheEntry<Cortege<HashSet<int>, HashSet<string>>>(Const.Cache.GroupsCache + rootWebUrl.GetHashCodeX());
		}

		/// <summary>
		/// Loads group cache
		/// </summary>
		private static Cortege<HashSet<int>, HashSet<string>> LoadGroupsCache(SPWeb rootWeb)
		{
			var groupIds = new HashSet<int>();
			var groupNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

			rootWeb.RunElevated(
				delegate
				{
					foreach (SPGroup siteGroup in SPH.GetElevatedWeb().SiteGroups)
					{
						groupIds.Add(siteGroup.ID);
						groupNames.Add(siteGroup.Name);
					}
				});

			return new Cortege<HashSet<int>, HashSet<string>>(groupIds, groupNames);
		}

		/// <summary>
		/// Returns all groups ids
		/// </summary>
		public static HashSet<int> GetSiteGroupIds(this SPWeb web)
		{
			var rootWeb = web.GetRootWeb();

			var cache = GetGroupsCacheEntry(rootWeb.ServerRelativeUrl);

			return cache.GetOrLoad(() => LoadGroupsCache(rootWeb)).Arg1;
		}

		/// <summary>
		/// Returns all groups names
		/// </summary>
		public static HashSet<string> GetSiteGroupNames(this SPWeb web)
		{
			var rootWeb = web.GetRootWeb();

			var cache = GetGroupsCacheEntry(rootWeb.ServerRelativeUrl);

			return cache.GetOrLoad(() => LoadGroupsCache(rootWeb)).Arg2;
		}

		/// <summary>
		/// Adds new group
		/// </summary>
		public static SPGroup AddGroup(this SPWeb web, string groupName, string description, SPMember owner, SPRoleType roleType)
		{
			SPGroup siteGroup = null;

			try
			{
				web.GetRootWeb().RunElevated(
					delegate
					{
						var rootWeb = SPH.GetElevatedWeb();
						var dWeb = SPH.GetElevatedWeb(web.ID);
						var eWeb = (SPWeb)dWeb;

						using (dWeb)
						using (new UnsafeUpdate(eWeb))
						using (new UnsafeUpdate(rootWeb))
						{
							var siteGroups = rootWeb.SiteGroups;

							siteGroups.Add(groupName, owner, SPH.GetCurrentUser(), description);

							var cache = GetGroupsCacheEntry(rootWeb.ServerRelativeUrl);

							cache.RemoveFromCache();

							siteGroup = siteGroups[groupName];

							try
							{
								eWeb.AssociatedGroups.Add(siteGroup);
								eWeb.Update();
							}
							catch (Exception x)
							{
								H.LX("AddGroup.AssociatedGroups", x);
							}

							if (eWeb.HasUniqueRoleAssignments)
								try
								{
									var webAssignment = new SPRoleAssignment(siteGroup);

									webAssignment.RoleDefinitionBindings.Add(eWeb.RoleDefinitions.GetByType(roleType));

									eWeb.RoleAssignments.Add(webAssignment);
								}
								catch (Exception x)
								{
									H.LX("AddGroup.HasUniqueRoleAssignments", x);
								}
						}
					});
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.AddGroup", x);

				siteGroup = null;
			}

			return siteGroup;
		}

		/// <summary>
		/// Adds new group
		/// </summary>
		public static SPGroup AddGroup(this SPWeb web, string groupName, string description, SPMember owner)
		{
			return AddGroup(web, groupName, description, owner, SPRoleType.Reader);
		}

		/// <summary>
		/// Return group by name
		/// </summary>
		public static SPGroup GetSiteGroup(this SPWeb web, string groupName)
		{
			if (groupName.IsNullOrEmpty() || web == null) return null;

			var rootWeb = web.GetRootWeb();

			try
			{
				var groups = rootWeb.GetSiteGroupNames();

				if (groups.Contains(groupName))
					return rootWeb.SiteGroups[groupName];
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.GetSiteGroup", "GetSiteGroup groupName: " + groupName, x);
			}

			return null;
		}

		/// <summary>
		/// Return group by id
		/// </summary>
		public static SPGroup GetSiteGroup(this SPWeb web, int groupId)
		{
			if (groupId <= 0 || web == null) return null;

			var rootWeb = web.GetRootWeb();

			try
			{
				var groups = rootWeb.GetSiteGroupIds();

				if (groups.Contains(groupId))
					return rootWeb.SiteGroups.GetByID(groupId);
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.GetSiteGroup", "GetSiteGroup groupId: " + groupId, x);
			}

			return null;
		}

		/// <summary>
		/// Ensures group by name, creates if not found
		/// </summary>
		public static SPGroup EnsureGroup(this SPWeb web, string groupName)
		{
			return EnsureGroup(web, groupName, String.Empty, web.CurrentUser, SPRoleType.Reader);
		}

		/// <summary>
		/// Ensures group by name, creates if not found
		/// </summary>
		public static SPGroup EnsureGroup(this SPWeb web, string groupName, string description, SPMember owner)
		{
			return EnsureGroup(web, groupName, description, owner, SPRoleType.Reader);
		}

		/// <summary>
		/// Ensures group by name, creates if not found
		/// </summary>
		public static SPGroup EnsureGroup(this SPWeb web, string groupName, string description, SPMember owner, SPRoleType roleType)
		{
			var group = web.GetSiteGroup(groupName) ?? web.AddGroup(groupName, description, owner, roleType);

			return @group;
		}

		/// <summary>
		/// Ensure <paramref name="group"/> contains <paramref name="users"/>, <paramref name="restrict"/> if specified
		/// </summary>
		/// <param name="group">Group to ensure users in</param>
		/// <param name="users">Collection of users</param>
		/// <param name="restrict">Option to restrict group members only to specified <paramref name="users"/> and to remove others</param>
		/// <exception cref="TargetInvocationException">Can't add user to group</exception>
		/// <exception cref="TargetInvocationException">Can't remove user from group</exception>
		public static void EnsureGroupUsers(this SPGroup group, IEnumerable<SPUser> users, bool restrict)
		{
			// Validate users (unique, not null)
			var newUsers = new Dictionary<int, SPUser>();

			foreach (var user in users)
				if (user != null)
					newUsers[user.ID] = user;

			var groupUsers = @group.Users.Cast<SPUser>().ToDictionary(user => user.ID);

			// Add new users
			foreach (var newUser in newUsers)
			{
				if (groupUsers.ContainsKey(newUser.Key))
					continue;

				try
				{
					@group.AddUser(newUser.Value);
				}
				catch (Exception x)
				{
					H.LX("SPPrincipalExts.EnsureUsers.A", x);
					throw new TargetInvocationException(String.Format("Can't add user {0} to group {1}", newUser.Value.LoginName, @group.Name), x);
				}
			}

			if (!restrict)
				return;

			// Keep owner
			newUsers[@group.Owner.ID] = null;

			// Remove old users
			foreach (var oldUser in groupUsers)
			{
				if (newUsers.ContainsKey(oldUser.Key))
					continue;

				try
				{
					@group.RemoveUser(oldUser.Value);
				}
				catch (Exception x)
				{
					H.LX("SPPrincipalExts.EnsureUsers.R", x);
					throw new TargetInvocationException(String.Format("Can't remove user {0} from group {1}", oldUser.Value.LoginName, @group.Name), x);
				}
			}
		}

		/// <summary>
		/// Ensures <paramref name="group"/> contains <paramref name="users"/>, <paramref name="restrict"/> if specified
		/// </summary>
		/// <exception cref="TargetInvocationException">Can't add user by id to group</exception>
		/// <exception cref="TargetInvocationException">Can't remove user from group</exception>
		public static void EnsureGroupUsers(this SPGroup group, IEnumerable<int> users, bool restrict)
		{
			// Validate users (unique, not null)
			var newUsers = new HashSet<int>();

			foreach (var user in users)
				if (user > 0)
					newUsers.Add(user);

			SPUserCollection allUsers = null;

			var groupUsers = @group.Users.Cast<SPUser>().ToDictionary(user => user.ID);

			// Add new users
			foreach (var newUser in newUsers)
			{
				if (groupUsers.ContainsKey(newUser))
					continue;

				try
				{
					if (allUsers == null)
						allUsers = @group.ParentWeb.GetRootWeb().AllUsers;

					@group.AddUser(allUsers.GetByID(newUser));
				}
				catch (Exception x)
				{
					H.LX("SPPrincipalExts.EnsureGroupUsers.A", x);
					throw new TargetInvocationException(String.Format("Can't add user id {0} to group {1}", newUser, @group.Name), x);
				}
			}

			if (!restrict)
				return;

			// Keep owner
			newUsers.Add(@group.Owner.ID);

			// Remove old users
			foreach (var oldUser in groupUsers)
			{
				if (newUsers.Contains(oldUser.Key))
					continue;

				try
				{
					@group.RemoveUser(oldUser.Value);
				}
				catch (Exception x)
				{
					H.LX("SPPrincipalExts.EnsureGroupUsers.R", x);
					throw new TargetInvocationException(String.Format("Can't remove user {0} from group {1}", oldUser.Value.LoginName, @group.Name), x);
				}
			}
		}

		#endregion

		#region Users

		/// <summary>
		/// Returns user from <paramref name="users"/> collection by <paramref name="userId"/>
		/// </summary>
		public static SPUser SafeGetUser(this SPUserCollection users, int userId)
		{
			try
			{
				return users.GetByID(userId);
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.SafeGetUserById", x);
				return null;
			}
		}

		/// <summary>
		/// Returns user from <paramref name="users"/> collection by <paramref name="loginName"/>
		/// </summary>
		public static SPUser SafeGetUser(this SPUserCollection users, string loginName)
		{
			try
			{
				return users[loginName];
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.SafeGetUserById", x);
				return null;
			}
		}

		/// <summary>
		/// Checks whether <paramref name="users"/> collection contains user by <paramref name="userId"/>
		/// </summary>
		public static bool ContainsUser(this SPUserCollection users, int userId)
		{
			try
			{
				return users.GetByID(userId) != null;
			}
			catch (Exception x)
			{
				H.LT("SPPrincipalExts.ContainsUser.{0}: {1}, {2}", x.GetType().Name, userId, x.Message);
				return false;
			}
		}

		/// <summary>
		/// Checks whether <paramref name="users"/> collection contains user by <paramref name="loginName"/>
		/// </summary>
		public static bool ContainsUser(this SPUserCollection users, string loginName)
		{
			try
			{
				return users[loginName] != null;
			}
			catch (Exception x)
			{
				H.LT("SPPrincipalExts.ContainsUser.{0}: {1}, {2}", x.GetType().Name, loginName, x.Message);
				return false;
			}
		}

		/// <summary>
		/// Formats user lookup value (i.e. "id;#name")
		/// </summary>
		public static string FormatLookupValue(this SPUser user)
		{
			return user != null ? SPH.FormatLookupValue(user.ID, user.Name) : null;
		}

		/// <summary>
		/// Returns user's email by login
		/// </summary>
		public static string GetUserEmail(this SPWeb web, string login)
		{
			var user = web.GetUser(login);

			return user != null ? user.Email : null;
		}

		/// <summary>
		/// Returns user by login
		/// </summary>
		public static SPUser GetUser(this SPWeb web, string login)
		{
			if (login.IsNullOrEmpty() || web == null) return null;

			var rootWeb = web.GetRootWeb();

			try
			{
				return rootWeb.AllUsers[login];
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.GetUser", "GetUser login: " + login, x);
				return null;
			}
		}

		/// <summary>
		/// Returns user by id
		/// </summary>
		public static SPUser GetUser(this SPWeb web, int accountId)
		{
			if (accountId <= 0 || web == null) return null;

			var rootWeb = web.GetRootWeb();

			try
			{
				return rootWeb.AllUsers.GetByID(accountId);
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.GetUser", "GetUser accountId: " + accountId, x);
				return null;
			}
		}

		/// <summary>
		/// Returns principal (user or group) by login/name
		/// </summary>
		public static SPPrincipal GetPrincipal(this SPWeb web, string login)
		{
			if (login.IsNullOrEmpty() || web == null)
				return null;

			return (SPPrincipal)web.GetSiteGroup(login) ?? (SPPrincipal)web.GetUser(login);
		}

		/// <summary>
		/// Returns principal (user or group) by login/name, ensures user on specified web
		/// </summary>
		public static SPPrincipal GetPrincipalAndEnsure(this SPWeb web, string login)
		{
			if (login.IsNullOrEmpty() || web == null)
				return null;

			return (SPPrincipal)web.GetSiteGroup(login) ?? (SPPrincipal)web.SafeEnsureUser(login);
		}

		/// <summary>
		/// Returns principal (user or group) by id
		/// </summary>
		public static SPPrincipal GetPrincipal(this SPWeb web, int id)
		{
			if (id <= 0 || web == null)
				return null;

			return (SPPrincipal)web.GetSiteGroup(id) ?? (SPPrincipal)web.GetUser(id);
		}

		/// <summary>
		/// Returns principal (user or group) by key (login/name/id), checks <paramref name="localCache"/>, uses <paramref name="loader"/> to find principal
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="localCache"/> is null</exception>
		/// <exception cref="ArgumentNullException"><paramref name="loader"/> is null</exception>
		public static SPPrincipal GetPrincipalCached<TKeyType>(this IDictionary<TKeyType, SPPrincipal> localCache, SPWeb web, TKeyType principalKey, Func<SPWeb, TKeyType, SPPrincipal> loader)
		{
			if (localCache == null) throw new ArgumentNullException("localCache");
			if (loader == null) throw new ArgumentNullException("loader");

			return localCache.SafeGetLoad(principalKey, key => loader(web, key));
		}

		/// <summary>
		/// Returns principal (user or group) by login/name, checks <paramref name="localCache"/>
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="localCache"/> is null</exception>
		public static SPPrincipal GetPrincipalCached(this IDictionary<string, SPPrincipal> localCache, SPWeb web, string login)
		{
			return localCache.GetPrincipalCached(web, login, GetPrincipal);
		}

		/// <summary>
		/// Returns principal (user or group) by login/name, ensures user on specified web, checks <paramref name="localCache"/>
		/// </summary>
		/// <exception cref="ArgumentNullException"><paramref name="localCache"/> is null</exception>
		public static SPPrincipal GetPrincipalAndEnsureCached(this IDictionary<string, SPPrincipal> localCache, SPWeb web, string login)
		{
			return localCache.GetPrincipalCached(web, login, GetPrincipalAndEnsure);
		}

		/// <summary>
		/// Return emails for <paramref name="principal"/>
		/// </summary>
		public static string GetEmails(this SPWeb web, string principal)
		{
			return web.GetPrincipal(principal).GetEmails();
		}

		/// <summary>
		/// Return emails for <paramref name="principalId"/>
		/// </summary>
		public static string GetEmails(this SPWeb web, int principalId)
		{
			return web.GetPrincipal(principalId).GetEmails();
		}

		/// <summary>
		/// Return emails for <paramref name="principal"/>
		/// </summary>
		public static string GetEmails(this SPPrincipal principal)
		{
			try
			{
				var group = principal as SPGroup;

				if (@group != null)
				{
					return
						@group.Users.OfType<SPUser>().Select(spUser => GetEmails(spUser)).Where(s => !s.IsNullOrEmpty()).CastToString(";");
				}

				var user = principal as SPUser;

				if (user != null)
				{
					return
						user.Email;
				}
			}
			catch (Exception x)
			{
				H.LX("SPPrincipalExts.GetEmails", x);
			}

			return String.Empty;
		}

		/// <summary>
		/// Removes claims prefix from <paramref name="login"/>
		/// </summary>
		public static string RemoveClaimsPrefix(string login)
		{
			return login.SubstringFrom('|');
		}

		/// <summary>
		/// Checks whether current user is specified in <paramref name="field"/>.
		/// Item is loaded from list by <paramref name="itemId"/> with specified <paramref name="contentTypeId"/>.
		/// List is loaded from <paramref name="web"/> by <paramref name="listIdOrTitle"/>.
		/// </summary>
		public static bool IsCurrentUserIn(this SPWeb web, string listIdOrTitle, int itemId, SPContentTypeId contentTypeId, string field)
		{
			var list = web.GetListByName(listIdOrTitle);

			if (list != null)
			{
				var item = list.GetItem(itemId);

				if (item != null && item.ContentTypeId.IsChildOrEqual(contentTypeId))
					return item.GetContainsPrincipal(field, SPH.GetCurrentUser().ID);
			}

			return false;
		}

		/// <summary>
		/// Checks whether current user is specified in <paramref name="field"/>.
		/// Item is loaded from list by <paramref name="itemId"/>.
		/// List is loaded from <paramref name="web"/> by <paramref name="listIdOrTitle"/>.
		/// </summary>
		public static bool IsCurrentUserIn(this SPWeb web, string listIdOrTitle, int itemId, string field)
		{
			var list = web.GetListByName(listIdOrTitle);

			if (list != null)
			{
				var item = list.GetItem(itemId);

				if (item != null)
					return item.GetContainsPrincipal(field, SPH.GetCurrentUser().ID);
			}

			return false;
		}

		/// <summary>
		/// Checks whether current user is in <paramref name="groupName"/>.
		/// </summary>
		public static bool IsCurrentUserInSiteGroup(this SPWeb web, string groupName)
		{
			var siteGroup = web.GetSiteGroup(groupName);

			return siteGroup != null && siteGroup.ContainsCurrentUser;
		}

		/// <summary>
		/// Checks whether specified user is in group by <paramref name="userId"/>.
		/// Group is loaded from <paramref name="web"/> SiteGroups by <paramref name="groupName"/>.
		/// </summary>
		public static bool IsUserInSiteGroup(this SPWeb web, string groupName, int userId)
		{
			var siteGroup = web.GetSiteGroup(groupName);

			return siteGroup != null && siteGroup.Users.ContainsUser(userId);
		}

		/// <summary>
		/// Checks whether <paramref name="user"/> is in group by <paramref name="groupName"/>
		/// </summary>
		public static bool InGroup(this SPUser user, string groupName)
		{
			return user != null && user.Groups.ContainsGroup(groupName);
		}

		/// <summary>
		/// Checks whether <paramref name="user"/> is in group by <paramref name="groupId"/>
		/// </summary>
		public static bool InGroup(this SPUser user, int groupId)
		{
			return user != null && user.Groups.ContainsGroup(groupId);
		}

		/// <summary>
		/// Checks whether <paramref name="principalId"/> is <paramref name="user"/> himself or is <paramref name="user"/> in group by <paramref name="principalId"/>
		/// </summary>
		public static bool IsHimselfOrHisGroup(this SPUser user, int principalId)
		{
			return user != null && (user.ID == principalId || user.InGroup(principalId));
		}

		/// <summary>
		/// Checks whether <paramref name="loginOrName"/> is <paramref name="user"/> himself or is <paramref name="user"/> in group by <paramref name="loginOrName"/>
		/// </summary>
		public static bool IsHimselfOrHisGroup(this SPUser user, string loginOrName)
		{
			return user != null && (user.Name.EqIC(loginOrName) || user.LoginName.EqIC(loginOrName) || user.InGroup(loginOrName));
		}

		#endregion
	}
}